/**
 * Copyright (c) 2021-2022 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "plugins/ecmascript/runtime/base/builtins_base.h"
#include <random>
#include <sstream>
#include <string>
#include <vector>
#include "plugins/ecmascript/runtime/base/number_helper.h"
#include "plugins/ecmascript/runtime/base/string_helper.h"
#include "plugins/ecmascript/runtime/ecma_macros.h"
#include "plugins/ecmascript/runtime/internal_call_params.h"
#include "plugins/ecmascript/runtime/interpreter/fast_runtime_stub-inl.h"
#include "plugins/ecmascript/runtime/interpreter/slow_runtime_helper.h"
#include "plugins/ecmascript/runtime/js_invoker.h"
#include "plugins/ecmascript/runtime/js_eval.h"
#include "plugins/ecmascript/runtime/js_array.h"
#include "plugins/ecmascript/runtime/tagged_array-inl.h"
#include "plugins/ecmascript/runtime/ecma_global_storage-inl.h"
#include "include/thread_scopes.h"

namespace ark::ecmascript::builtins {

static constexpr uint8_t BIT_MASK = 0x0F;
static constexpr uint8_t BIT_MASK_FF = 0xFF;
static constexpr uint16_t BIT16_MASK = 0x3FF;
static constexpr uint8_t BIT_MASK_ONE = 0x80;
static constexpr uint8_t BIT_MASK_TWO = 0xC0;
using JudgUriFunc = bool (*)(uint16_t);

using NumberHelper = ecmascript::base::NumberHelper;
using StringHelper = ecmascript::base::StringHelper;

static void PrintString(JSThread *thread, EcmaString *string);
static JSTaggedValue Encode(JSThread *thread, const JSHandle<EcmaString> &str, JudgUriFunc isInUriSet);
static JSTaggedValue Decode(JSThread *thread, const JSHandle<EcmaString> &str, JudgUriFunc isInUriSet);

inline bool IsAlNum(uint16_t ch)
{
    return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9');
}
inline bool IsInNonEscapedSymbols(uint16_t ch)
{
    switch (ch) {
        case '@':
        case '*':
        case '_':
        case '+':
        case '-':
        case '.':
        case '/':
            return true;
        default:
            return false;
    }
}
inline bool IsNotEscaped(uint16_t ch)
{
    return IsAlNum(ch) || IsInNonEscapedSymbols(ch);
}
bool IsUnescapedURI(uint16_t ch);
bool IsInUnescapedURISet(uint16_t ch);
bool IsInReservedURISet(uint16_t ch);
bool IsReservedURI(uint16_t ch);
bool IsInMarkURISet(uint16_t ch);
bool IsHexDigits(uint16_t ch);
inline int IsHex(uint16_t cc)
{
    constexpr uint16_t HEX_MIN_LETTER_VALUE = 10;
    constexpr uint16_t HEX_MAX_LETTER_VALUE = 15;
    if (cc > 'f') {
        return -1;
    }
    cc -= '0';
    if (cc < HEX_MIN_LETTER_VALUE) {
        return cc;
    }
    cc -= ('A' - '0');
    if (cc <= HEX_MAX_LETTER_VALUE - HEX_MIN_LETTER_VALUE) {
        return cc + HEX_MIN_LETTER_VALUE;
    }
    cc -= ('a' - 'A');
    if (cc <= HEX_MAX_LETTER_VALUE - HEX_MIN_LETTER_VALUE) {
        return cc + HEX_MIN_LETTER_VALUE;
    }
    return -1;
}
uint8_t GetValueFromTwoHex(uint16_t front, uint16_t behind);
JSHandle<JSTaggedValue> GCSpecialisedObjectSpaceType(JSThread *thread, const JSHandle<JSTaggedValue> &objHandle);

/**
 * Class tracks GC tasks already processed by GC.
 * Also the class tracks concurrent mark GC phase and calls
 * the callback if it specified.
 */
class GCTaskTracker : public mem::GCListener {
public:
    void InitIfNeeded(mem::GC *gc);
    bool IsInitialized();
    void AddTaskId(uint64_t id);
    bool HasId(uint64_t id);
    void SetCallbackForTask(uint32_t taskId, uintptr_t callbackHandle);
    void GCStarted(const GCTask &task, size_t heapSize) override;
    void GCFinished(const GCTask &task, size_t heapSizeBeforeGc, size_t heapSize) override;
    void GCPhaseStarted(mem::GCPhase phase) override;
    void RemoveId(uint64_t id);

private:
    bool initialized_ = false;
    std::vector<uint64_t> taskIds_ GUARDED_BY(lock_);
    uint32_t currentTaskId_ = 0;
    uint32_t callbackTaskId_ = 0;
    uintptr_t callbackHandle_ = 0;
    os::memory::Mutex lock_;
};

// NOLINTNEXTLINE(fuchsia-statically-constructed-objects)
GCTaskTracker g_gGctaskTracker;

void GCTaskTracker::InitIfNeeded(mem::GC *gc)
{
    if (initialized_) {
        return;
    }
    gc->AddListener(this);
    initialized_ = true;
}

bool GCTaskTracker::IsInitialized()
{
    return initialized_;
}

void GCTaskTracker::AddTaskId(uint64_t id)
{
    os::memory::LockHolder lock(lock_);
    taskIds_.push_back(id);
}

bool GCTaskTracker::HasId(uint64_t id)
{
    os::memory::LockHolder lock(lock_);
    return std::find(taskIds_.begin(), taskIds_.end(), id) != taskIds_.end();
}

void GCTaskTracker::SetCallbackForTask(uint32_t taskId, uintptr_t callbackHandle)
{
    callbackTaskId_ = taskId;
    callbackHandle_ = callbackHandle;
}

void GCTaskTracker::GCPhaseStarted(mem::GCPhase phase)
{
    if (phase == mem::GCPhase::GC_PHASE_MARK && callbackHandle_ != 0 && currentTaskId_ == callbackTaskId_) {
        JSThread *thread = JSThread::GetCurrent();
        JSHandle<JSTaggedValue> callback(thread, *reinterpret_cast<JSTaggedValue *>(callbackHandle_));
        JSHandle<JSFunction> fn = JSHandle<JSFunction>::Cast(callback);
        JSHandle<JSTaggedValue> global(thread, thread->GetGlobalObject());
        JSHandle<JSTaggedValue> newTarget(thread, JSTaggedValue::Undefined());
        auto info = NewRuntimeCallInfo(thread, fn, global, JSTaggedValue::Undefined(), 1);
        // JS has only one thread, therefore, we run this callback from the mutator thread during concurrent marking.
        ASSERT(!thread->GetVM()->GetMutatorLock()->HasLock());
        os::memory::ReadLockHolder lock(*thread->GetVM()->GetMutatorLock());
        info->SetCallArgs(thread->GetEcmaVM()->GetGlobalEnv()->GetGcMarker());
        JSFunction::Call(info.Get());
    }
}

void GCTaskTracker::GCStarted(const GCTask &task, [[maybe_unused]] size_t heapSize)
{
    currentTaskId_ = task.GetId();
}

void GCTaskTracker::GCFinished(const GCTask &task, [[maybe_unused]] size_t heapSizeBeforeGc,
                               [[maybe_unused]] size_t heapSize)
{
    RemoveId(task.GetId());
}

void GCTaskTracker::RemoveId(uint64_t id)
{
    currentTaskId_ = 0;
    if (id == callbackTaskId_ && callbackHandle_ != 0) {
        JSThread::GetCurrent()->GetEcmaGlobalStorage()->DisposeGlobalHandle(callbackHandle_);
        callbackHandle_ = 0;
    }
    if (id != 0) {
        os::memory::LockHolder lock(lock_);
        auto it = std::find(taskIds_.begin(), taskIds_.end(), id);
        // There may be no such id if the corresponding GC has been triggered not by startGC
        if (it != taskIds_.end()) {
            taskIds_.erase(it);
        }
    }
}

// 18.2.1
JSTaggedValue global::Eval(EcmaRuntimeCallInfo *argv)
{
    JSThread *thread = argv->GetThread();
    [[maybe_unused]] EcmaHandleScope handleScope(thread);
    if (argv->GetArgsNumber() == 0) {
        return JSTaggedValue(JSTaggedValue::VALUE_UNDEFINED);
    }

    return EvalUtils::Eval(thread, argv->GetCallArg(0));
}

// 18.2.2
JSTaggedValue global::IsFinite(EcmaRuntimeCallInfo *argv)
{
    ASSERT(argv);
    JSThread *thread = argv->GetThread();
    BUILTINS_API_TRACE(thread, Global, IsFinite);
    [[maybe_unused]] EcmaHandleScope handleScope(thread);
    JSHandle<JSTaggedValue> numberInput = builtins_common::GetCallArg(argv, 0);
    // 1. Let num be ToNumber(number).
    JSTaggedNumber number = JSTaggedValue::ToNumber(thread, numberInput);
    RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
    // 3. If num is NaN, +Infinite, or -Infinite, return false.
    // 4. Otherwise, return true.
    if (std::isfinite(number.GetNumber())) {
        return builtins_common::GetTaggedBoolean(true);
    }
    return builtins_common::GetTaggedBoolean(false);
}

// 18.2.3
JSTaggedValue global::IsNaN(EcmaRuntimeCallInfo *argv)
{
    ASSERT(argv);
    JSThread *thread = argv->GetThread();
    BUILTINS_API_TRACE(thread, Global, IsNaN);
    [[maybe_unused]] EcmaHandleScope handleScope(thread);
    JSHandle<JSTaggedValue> numberInput = builtins_common::GetCallArg(argv, 0);
    // 1. Let num be ToNumber(number).
    JSTaggedNumber number = JSTaggedValue::ToNumber(thread, numberInput);
    RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);

    // 3. If num is NaN, return true.
    if (std::isnan(number.GetNumber())) {
        return builtins_common::GetTaggedBoolean(true);
    }
    // 4. Otherwise, return false.
    return builtins_common::GetTaggedBoolean(false);
}

bool IsUnescapedURI(uint16_t ch)
{
    return IsAlNum(ch) || IsInMarkURISet(ch);
}

bool IsInUnescapedURISet(uint16_t ch)
{
    if (ch == '#') {
        return true;
    }
    return IsUnescapedURI(ch) || IsReservedURI(ch);
}

bool IsInReservedURISet(uint16_t ch)
{
    if (ch == '#') {
        return true;
    }
    return IsReservedURI(ch);
}

bool IsReservedURI(uint16_t ch)
{
    std::u16string str(u";/?:@&=+$,");
    return (str.find(ch) != std::u16string::npos);
}

bool IsInMarkURISet(uint16_t ch)
{
    std::u16string str(u"-_.!~*'()");
    return (str.find(ch) != std::u16string::npos);
}

bool IsHexDigits(uint16_t ch)
{
    return ('0' <= ch && ch <= '9') || ('A' <= ch && ch <= 'F') || ('a' <= ch && ch <= 'f');
}

JSTaggedValue global::Unescape(EcmaRuntimeCallInfo *argv)
{
    ASSERT(argv);
    constexpr uint16_t PERCENT_SIGN = 0x0025;
    constexpr uint16_t LATIN_SMALL_LETTER_U = 0x0075;
    JSThread *thread = argv->GetThread();
    BUILTINS_API_TRACE(thread, Global, Unescape);
    [[maybe_unused]] EcmaHandleScope handleScope(thread);
    // 1. Set string to ? ToString(string).
    [[maybe_unused]] JSHandle<EcmaString> sstring =
        JSTaggedValue::ToString(thread, builtins_common::GetCallArg(argv, 0));
    // 2. ReturnIfAbrupt(uriString).
    RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
    // 2. Let length be the length of string.
    const int32_t length = sstring->GetLength();
    // 3. Let R be the empty String.
    ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
    std::u16string rr;
    // 4. Let k be 0.
    // 5. Repeat, while k != length
    for (int32_t k = 0; k != length; ++k) {
        // a. Let char be the code unit (represented as a 16-bit unsigned integer) at index k within string.
        uint16_t cc = sstring->At(k);
        // b. If c is the code unit 0x0025 (PERCENT_SIGN) then
        if (cc != PERCENT_SIGN) {
            // c. Set R to the string-concatenation of R and c.
            rr.append(StringHelper::Utf16ToU16String(&cc, 1));
            continue;
        }
        // i. Let hexEscape be the empty String.
        std::u16string hexEscape;
        // ii. Let skip be 0.
        auto skip = 0;
        // iii. If k ≤ length - 6 and the code unit at index k + 1 within string is the code unit 0x0075
        // (LATIN_SMALL_LETTER_U), then
        bool isValidHexSequence = false;
        int resChar = 0;
        if (k <= length - 6 && sstring->At(k + 1) == LATIN_SMALL_LETTER_U) {
            // 1. Set hexEscape to the substring of string from k + 2 to k + 6
            hexEscape = StringHelper::StringToU16string(StringHelper::SubString(sstring, k + 2, 4));
            // 2. Set skip to 5
            skip = 5;
            // check if hex sequence is valid
            int digit1 = IsHex(hexEscape[0]);
            int digit2 = IsHex(hexEscape[1]);
            int digit3 = IsHex(hexEscape[2]);
            int digit4 = IsHex(hexEscape[3]);
            if (digit1 != -1 && digit2 != -1 && digit3 != -1 && digit4 != -1) {
                // NOLINTNEXTLINE(hicpp-signed-bitwise,readability-magic-numbers)
                resChar = (digit1 << 12) + (digit2 << 8) + (digit3 << 4) + digit4;
                isValidHexSequence = true;
            }
        }
        // iv. Else if k ≤ length - 3, then
        else if (k <= length - 3) {
            // 1. Set hexEscape to the substring of string from k + 1 to k + 3.
            hexEscape = StringHelper::StringToU16string(StringHelper::SubString(sstring, k + 1, 2));
            // 2. Set skip to 2.
            skip = 2;
            // check if hex sequence is valid
            int digit1 = IsHex(hexEscape[0]);
            int digit2 = IsHex(hexEscape[1]);

            if (digit1 != -1 && digit2 != -1) {
                // NOLINTNEXTLINE(hicpp-signed-bitwise,readability-magic-numbers)
                resChar = (digit1 << 4) + digit2;
                isValidHexSequence = true;
            }
        }
        // v. If hexEscape can be interpreted as an expansion of HexDigits[~Sep], then
        if (isValidHexSequence) {
            // 1. Let hexIntegerLiteral be the string-concatenation of "0x" and hexEscape.
            // 2. Let n be ! ToNumber(hexIntegerLiteral).
            // 3. Set c to the code unit whose value is ℝ(n).
            cc = resChar;
            // 4. Set k to k + skip
            k += skip;
        }
        // c. Set R to the string-concatenation of R and c.
        rr.append(StringHelper::Utf16ToU16String(&cc, 1));
    }
    // 6. Return R.
    auto *rrData = reinterpret_cast<uint16_t *>(rr.data());
    int32_t rrSize = rr.size();
    return factory->NewFromUtf16Literal(rrData, rrSize).GetTaggedValue();
}

JSTaggedValue global::Escape(EcmaRuntimeCallInfo *argv)
{
    ASSERT(argv);
    constexpr uint16_t MAX_DOUBLE_DIGIT = 256;
    static const std::u16string HEX_STR = u"0123456789ABCDEF";
    JSThread *thread = argv->GetThread();
    BUILTINS_API_TRACE(thread, Global, Escape);
    [[maybe_unused]] EcmaHandleScope handleScope(thread);
    // 1. Set string to ? ToString(string).
    [[maybe_unused]] JSHandle<EcmaString> sstring =
        JSTaggedValue::ToString(thread, builtins_common::GetCallArg(argv, 0));
    // ReturnIfAbrupt(uriString).
    RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
    // 2. Let length be the length of string.
    const int32_t length = sstring->GetLength();
    // 3. Let R be the empty String.
    ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
    std::u16string rr;
    // 4. Let k be 0.
    // 5. Repeat, while k < length
    for (int32_t k = 0; k < length; ++k) {
        // a. Let char be the code unit (represented as a 16-bit unsigned integer) at index k within string.
        uint16_t cc = sstring->At(k);
        std::u16string ss;
        // b. If char is one of the code units in
        // "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789@*_+-./", then
        if (IsNotEscaped(cc)) {
            // i. Let S be the String value containing the single code unit char.
            ss = StringHelper::Utf16ToU16String(&cc, 1);
        }
        // c. Else if char ≥ 256, then
        else if (cc >= MAX_DOUBLE_DIGIT) {
            // i. Let n be the numeric value of char.
            // ii. Let S be the string-concatenation of:
            //     * "%u"
            ss = u"%u";
            //     * the String representation of n, formatted as a four-digit uppercase hexadecimal number, padded to
            //       the left with zeroes if necessary
            constexpr uint16_t N_DIGITS = 4;
            for (uint16_t j = 0; j < N_DIGITS; j++) {
                // NOLINTNEXTLINE(hicpp-signed-bitwise,readability-magic-numbers)
                uint16_t hexC = HEX_STR[cc >> ((N_DIGITS - 1 - j) * 4U) & BIT_MASK];
                ss.append(StringHelper::Utf16ToU16String(&hexC, 1));
            }
        }
        // d. Else
        else {
            // i. Assert: char < 256
            ASSERT(cc < MAX_DOUBLE_DIGIT);
            // ii. Let n be the numeric value of char
            // iii. Let S be the string-concatenation of:
            //     * "%"
            ss = u"%";
            //     * the String representation of n, formatted as a two-digit uppercase hexadecimal number, padded to
            //       the left with a zero if necessary
            constexpr uint16_t N_DIGITS = 2;
            for (uint16_t j = 0; j < N_DIGITS; j++) {
                // NOLINTNEXTLINE(hicpp-signed-bitwise,readability-magic-numbers)
                uint16_t hexC = HEX_STR[cc >> ((N_DIGITS - 1 - j) * 4U) & BIT_MASK];
                ss.append(StringHelper::Utf16ToU16String(&hexC, 1));
            }
        }
        // e. Set R to the string-concatenation of R and S.
        rr.append(ss);
        // f. Set k to k + 1
    }
    // 6. Return R.
    auto *rrData = reinterpret_cast<uint16_t *>(rr.data());
    int32_t rrSize = rr.size();
    return factory->NewFromUtf16Literal(rrData, rrSize).GetTaggedValue();
}

// 18.2.6
JSTaggedValue global::DecodeURI(EcmaRuntimeCallInfo *argv)
{
    ASSERT(argv);
    JSThread *thread = argv->GetThread();
    BUILTINS_API_TRACE(thread, Global, DecodeURI);
    [[maybe_unused]] EcmaHandleScope handleScope(thread);
    // 1. Let uriString be ToString(encodedURI).
    // 2. ReturnIfAbrupt(uriString).
    [[maybe_unused]] JSHandle<EcmaString> uriString =
        JSTaggedValue::ToString(thread, builtins_common::GetCallArg(argv, 0));
    RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);

    // 3. Let reservedURISet be a String containing one instance of each code unit valid in uriReserved plus "#".
    // 4. Return Decode(uriString, reservedURISet).
    return Decode(thread, uriString, IsInReservedURISet);
}

JSTaggedValue global::EncodeURI(EcmaRuntimeCallInfo *argv)
{
    ASSERT(argv);
    JSThread *thread = argv->GetThread();
    BUILTINS_API_TRACE(thread, Global, EncodeURI);
    [[maybe_unused]] EcmaHandleScope handleScope(thread);
    // 1. Let uriString be ToString(uri).
    // 2. ReturnIfAbrupt(uriString).
    [[maybe_unused]] JSHandle<EcmaString> uriString =
        JSTaggedValue::ToString(thread, builtins_common::GetCallArg(argv, 0));
    RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);

    // 3. Let unescapedURISet be a String containing one instance of
    //    each code unit valid in uriReserved and uriUnescaped plus "#".
    // 4. Return Encode(uriString, unescapedURISet).
    return Encode(thread, uriString, IsInUnescapedURISet);
}

JSTaggedValue global::DecodeURIComponent(EcmaRuntimeCallInfo *argv)
{
    ASSERT(argv);
    JSThread *thread = argv->GetThread();
    BUILTINS_API_TRACE(thread, Global, DecodeURIComponent);
    [[maybe_unused]] EcmaHandleScope handleScope(thread);
    // 1. Let componentString be ToString(encodedURIComponent).
    // 2. ReturnIfAbrupt(componentString).
    [[maybe_unused]] JSHandle<EcmaString> componentString =
        JSTaggedValue::ToString(thread, builtins_common::GetCallArg(argv, 0));
    RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);

    // 3. Let reservedURIComponentSet be the empty String.
    // 4. Return Decode(componentString, reservedURIComponentSet).
    return Decode(thread, componentString, []([[maybe_unused]] uint16_t unused) { return false; });
}

JSTaggedValue global::EncodeURIComponent(EcmaRuntimeCallInfo *argv)
{
    ASSERT(argv);
    JSThread *thread = argv->GetThread();
    BUILTINS_API_TRACE(thread, Global, EncodeURIComponent);
    [[maybe_unused]] EcmaHandleScope handleScope(thread);
    // 1. Let componentString be ToString(uriComponent).
    // 2. ReturnIfAbrupt(componentString).
    [[maybe_unused]] JSHandle<EcmaString> componentString =
        JSTaggedValue::ToString(thread, builtins_common::GetCallArg(argv, 0));
    RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);

    // 3. Let unescapedURIComponentSet be a String containing one instance of each code unit valid in uriUnescaped.
    // 4. Return Encode(componentString, unescapedURIComponentSet).
    return Encode(thread, componentString, IsUnescapedURI);
}

// Runtime Semantics
JSTaggedValue Encode(JSThread *thread, const JSHandle<EcmaString> &str, JudgUriFunc isInUriSet)
{
    // 1. Let strLen be the number of code units in string.
    uint32_t strLen = str->GetLength();
    // 2. Let R be the empty String.
    ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
    std::u16string resStr;

    // 3. Let k be 0.
    // 4. Repeat
    uint32_t k = 0;
    while (true) {
        // a. If k equals strLen, return R.
        if (k == strLen) {
            auto *uint16tData = reinterpret_cast<uint16_t *>(resStr.data());
            int32_t resSize = resStr.size();
            return factory->NewFromUtf16Literal(uint16tData, resSize).GetTaggedValue();
        }

        // b. Let C be the code unit at index k within string.
        // c. If C is in unescapedSet, then
        //   i. Let S be a String containing only the code unit C.
        //   ii. Let R be a new String value computed by concatenating the previous value of R and S.
        // d. Else C is not in unescapedSet,
        uint16_t cc = str->At(k);
        if (isInUriSet(cc)) {
            std::u16string sStr = StringHelper::Utf16ToU16String(&cc, 1);
            resStr.append(sStr);
        } else {
            // i. If the code unit value of C is not less than 0xDC00 and not greater than 0xDFFF,
            //    throw a URIError exception.
            if (cc >= utf::DECODE_TRAIL_LOW && cc <= utf::DECODE_TRAIL_HIGH) {
                THROW_URI_ERROR_AND_RETURN(thread, "EncodeURI: The format of the URI to be parsed is incorrect",
                                           JSTaggedValue::Exception());
            }

            // ii. If the code unit value of C is less than 0xD800 or greater than 0xDBFF, then
            //    1. Let V be the code unit value of C.
            // iii. Else,
            //    1. Increase k by 1.
            //    2. If k equals strLen, throw a URIError exception.
            //    3. Let kChar be the code unit value of the code unit at index k within string.
            //    4. If kChar is less than 0xDC00 or greater than 0xDFFF, throw a URIError exception.
            //    5. Let V be UTF16Decode(C, kChar).
            uint32_t vv;
            if (cc < utf::DECODE_LEAD_LOW || cc > utf::DECODE_LEAD_HIGH) {
                vv = cc;
            } else {
                k++;
                if (k == strLen) {
                    THROW_URI_ERROR_AND_RETURN(thread, "k is invalid", JSTaggedValue::Exception());
                }
                uint16_t kc = str->At(k);
                if (kc < utf::DECODE_TRAIL_LOW || kc > utf::DECODE_TRAIL_HIGH) {
                    THROW_URI_ERROR_AND_RETURN(thread, "EncodeURI: The format of the URI to be parsed is incorrect",
                                               JSTaggedValue::Exception());
                }
                vv = utf::UTF16Decode(cc, kc);
            }

            // iv. Let Octets be the array of octets resulting by applying the UTF-8 transformation to V,
            //     and let L be the array size.
            // v. Let j be 0.
            // vi. Repeat, while j < L
            //    1. Let jOctet be the value at index j within Octets.
            //    2. Let S be a String containing three code units "%XY" where XY are two uppercase hexadecimal
            //       digits encoding the value of jOctet.
            //    3. Let R be a new String value computed by concatenating the previous value of R and S.
            //    4. Increase j by 1.
            std::string oct = StringHelper::Utf32ToString(vv);
            std::string hexStr("0123456789ABCDEF");

            uint32_t length = oct.length();
            std::stringstream tmpStr;
            for (uint32_t j = 0; j < length; j++) {
                uint8_t joct = oct.at(j);
                tmpStr << '%' << hexStr.at((joct >> 4U) & BIT_MASK)  // NOLINT
                       << hexStr.at(joct & BIT_MASK);                // 4: means shift right by 4 digits
            }
            resStr.append(StringHelper::StringToU16string(tmpStr.str()));
        }

        // e. Increase k by 1.
        k++;
    }
}

uint8_t GetValueFromTwoHex(uint16_t front, uint16_t behind)
{
    ASSERT(IsHexDigits(front) && IsHexDigits(behind));
    std::u16string hexString(u"0123456789ABCDEF");

    size_t idxf = StringHelper::FindFromU16ToUpper(hexString, &front);
    size_t idxb = StringHelper::FindFromU16ToUpper(hexString, &behind);
    uint8_t res = ((idxf << 4U) | idxb) & BIT_MASK_FF;  // NOLINT 4: means shift left by 4 digits
    return res;
}

// Runtime Semantics
JSTaggedValue Decode(JSThread *thread, const JSHandle<EcmaString> &str, JudgUriFunc isInUriSet)
{
    // 1. Let strLen be the number of code units in string.
    [[maybe_unused]] uint32_t strLen = str->GetLength();
    // 2. Let R be the empty String.
    ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
    std::u16string resStr;

    // 3. Let k be 0.
    // 4. Repeat
    uint32_t k = 0;
    while (true) {
        // a. If k equals strLen, return R.
        if (k == strLen) {
            auto *uint16tData = reinterpret_cast<uint16_t *>(resStr.data());
            int32_t resSize = resStr.size();
            return factory->NewFromUtf16Literal(uint16tData, resSize).GetTaggedValue();
        }

        // b. Let C be the code unit at index k within string.
        // c. If C is not "%", then
        //    i. Let S be the String containing only the code unit C.
        // d. Else C is "%",
        //   i. Let start be k.
        //   iv. Let B be the 8-bit value represented by the two hexadecimal digits at index (k + 1) and (k + 2).
        //   v. Increment k by 2.
        //   vi. If the most significant bit in B is 0, then
        //      1. Let C be the code unit with code unit value B.
        //      2. If C is not in reservedSet, then
        //         a. Let S be the String containing only the code unit C.
        //      3. Else C is in reservedSet,
        //         a. Let S be the substring of string from index start to index k inclusive.
        uint16_t cc = str->At(k);
        std::u16string sStr;
        if (cc != '%') {
            if (cc == 0 && strLen == 1) {
                JSHandle<EcmaString> tmpEcmaString = factory->NewFromUtf16Literal(&cc, 1);
                return tmpEcmaString.GetTaggedValue();
            }
            sStr = StringHelper::Utf16ToU16String(&cc, 1);
        } else {
            [[maybe_unused]] uint32_t start = k;

            // ii. If k + 2 is greater than or equal to strLen, throw a URIError exception.
            // iii. If the code units at index (k+1) and (k + 2) within string do not represent hexadecimal digits,
            //      throw a URIError exception.
            if ((k + 2) >= strLen) {  // 2: means plus 2
                THROW_URI_ERROR_AND_RETURN(thread, "DecodeURI: The format of the URI to be parsed is incorrect",
                                           JSTaggedValue::Exception());
            }
            if (!(IsHexDigits(str->At(k + 1)) && IsHexDigits(str->At(k + 2)))) {  // 2: means plus 2
                THROW_URI_ERROR_AND_RETURN(thread, "DecodeURI: The format of the URI to be parsed is incorrect",
                                           JSTaggedValue::Exception());
            }

            uint16_t frontChar = str->At(k + 1);
            uint16_t behindChar = str->At(k + 2);  // 2: means plus 2
            uint8_t bb = GetValueFromTwoHex(frontChar, behindChar);
            k += 2;  // 2: means plus 2
            if ((bb & BIT_MASK_ONE) == 0) {
                if (!isInUriSet(bb)) {
                    sStr = StringHelper::Utf8ToU16String(&bb, 1);
                    if (bb == 0) {
                        return factory->NewFromUtf16Literal(reinterpret_cast<uint16_t *>(sStr.data()), 1)
                            .GetTaggedValue();
                    }
                } else {
                    sStr = StringHelper::StringToU16string(StringHelper::SubString(str, start, k - start + 1));
                }
            } else {
                // vii. Else the most significant bit in B is 1,
                //   1. Let n be the smallest nonnegative integer such that (B << n) & 0x80 is equal to 0.
                //   3. Let Octets be an array of 8-bit integers of size n.
                //   4. Put B into Octets at index 0.
                //   6. Let j be 1.
                //   7. Repeat, while j < n
                //     a. Increment k by 1.
                //     d. Let B be the 8-bit value represented by the two hexadecimal digits at
                //        index (k + 1) and (k + 2).
                //     f. Increment k by 2.
                //     g. Put B into Octets at index j.
                //     h. Increment j by 1.
                //   9. If V < 0x10000, then
                //     a. Let C be the code unit V.
                //     b. If C is not in reservedSet, then
                //        i. Let S be the String containing only the code unit C.
                //     c. Else C is in reservedSet,
                //        i. Let S be the substring of string from index start to index k inclusive.
                //   10. Else V ≥ 0x10000,
                //     a. Let L be (((V – 0x10000) & 0x3FF) + 0xDC00).
                //     b. Let H be ((((V – 0x10000) >> 10) & 0x3FF) + 0xD800).
                //     c. Let S be the String containing the two code units H and L.
                uint32_t n = 0;
                // NOLINTNEXTLINE(hicpp-signed-bitwise)
                while ((((bb << n) & BIT_MASK_ONE) != 0)) {
                    n++;
                    if (n > 4) {  // 4 : 4 means less than 4
                        break;
                    }
                }
                // 2. If n equals 1 or n is greater than 4, throw a URIError exception.
                if ((n == 1) || (n > 4)) {  // 4: means greater than 4
                    THROW_URI_ERROR_AND_RETURN(thread, "DecodeURI: The format of the URI to be parsed is incorrect",
                                               JSTaggedValue::Exception());
                }

                std::vector<uint8_t> oct = {bb};

                // 5. If k + (3 × (n – 1)) is greater than or equal to strLen, throw a URIError exception.
                if (k + (3 * (n - 1)) >= strLen) {  // 3: means multiply by 3
                    THROW_URI_ERROR_AND_RETURN(thread, "DecodeURI: The format of the URI to be parsed is incorrect",
                                               JSTaggedValue::Exception());
                }
                uint32_t j = 1;
                while (j < n) {
                    k++;
                    uint16_t codeUnit = str->At(k);
                    // b. If the code unit at index k within string is not "%", throw a URIError exception.
                    // c. If the code units at index (k +1) and (k + 2) within string do not represent hexadecimal
                    //    digits, throw a URIError exception.
                    if (!(codeUnit == '%')) {
                        THROW_URI_ERROR_AND_RETURN(thread, "DecodeURI: The format of the URI to be parsed is incorrect",
                                                   JSTaggedValue::Exception());
                    }
                    if (!(IsHexDigits(str->At(k + 1)) && IsHexDigits(str->At(k + 2)))) {  // 2: means plus 2
                        THROW_URI_ERROR_AND_RETURN(thread, "DecodeURI: The format of the URI to be parsed is incorrect",
                                                   JSTaggedValue::Exception());
                    }

                    uint16_t frontChart = str->At(k + 1);
                    uint16_t behindChart = str->At(k + 2);  // 2: means plus 2
                    bb = GetValueFromTwoHex(frontChart, behindChart);
                    // e. If the two most significant bits in B are not 10, throw a URIError exception.
                    if (!((bb & BIT_MASK_TWO) == BIT_MASK_ONE)) {
                        THROW_URI_ERROR_AND_RETURN(thread, "DecodeURI: The format of the URI to be parsed is incorrect",
                                                   JSTaggedValue::Exception());
                    }

                    k += 2;  // 2: means plus 2
                    oct.push_back(bb);
                    j++;
                }

                // 8. Let V be the value obtained by applying the UTF-8 transformation to Octets, that is,
                //     from an array of octets into a 21-bit value. If Octets does not contain a valid UTF-8 encoding of
                //     a Unicode code point throw a URIError exception.
                if (!utf::IsValidUTF8(oct)) {
                    THROW_URI_ERROR_AND_RETURN(thread, "DecodeURI: The format of the URI to be parsed is incorrect",
                                               JSTaggedValue::Exception());
                }
                uint32_t vv = StringHelper::Utf8ToU32String(oct);
                if (vv < utf::DECODE_SECOND_FACTOR) {
                    if (!isInUriSet(vv)) {
                        sStr = StringHelper::Utf16ToU16String(reinterpret_cast<uint16_t *>(&vv), 1);
                    } else {
                        sStr = StringHelper::StringToU16string(StringHelper::SubString(str, start, k - start + 1));
                    }
                } else {
                    uint16_t lv = (((vv - utf::DECODE_SECOND_FACTOR) & BIT16_MASK) + utf::DECODE_TRAIL_LOW);
                    uint16_t hv = ((((vv - utf::DECODE_SECOND_FACTOR) >> 10U) & BIT16_MASK) +  // NOLINT
                                   utf::DECODE_LEAD_LOW);  // 10: means shift left by 10 digits
                    sStr = StringHelper::Append(StringHelper::Utf16ToU16String(&hv, 1),
                                                StringHelper::Utf16ToU16String(&lv, 1));
                }
            }
        }
        // e. Let R be a new String value computed by concatenating the previous value of R and S.
        // f. Increase k by 1.
        resStr.append(sStr);
        k++;
    }
}

void PrintString([[maybe_unused]] JSThread *thread, EcmaString *string)
{
    if (string == nullptr) {
        return;
    }

    PandaString buffer = ConvertToPandaString(string);
    std::cout << buffer;
}

JSTaggedValue global::Print(EcmaRuntimeCallInfo *argv)
{
    if (argv == nullptr) {
        return JSTaggedValue::Undefined();
    }
    JSThread *thread = argv->GetThread();
    [[maybe_unused]] EcmaHandleScope handleScope(thread);
    BUILTINS_API_TRACE(thread, Global, Print);

    uint32_t numArgs = argv->GetArgsNumber();
    for (uint32_t i = 0; i < numArgs; i++) {
        JSHandle<EcmaString> stringContent = JSTaggedValue::ToString(thread, builtins_common::GetCallArg(argv, i));
        RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
        PrintString(thread, *stringContent);

        if (i != numArgs - 1) {
            std::cout << " ";
        }
    }
    std::cout << std::endl;
    return JSTaggedValue::Undefined();
}

JSTaggedValue global::Gc(EcmaRuntimeCallInfo *argv)
{
    JSThread *thread = argv->GetThread();
    ASSERT(thread != nullptr);
    [[maybe_unused]] EcmaHandleScope handleScope(thread);
    BUILTINS_API_TRACE(thread, Global, Gc);

    thread->GetEcmaVM()->GetGC()->WaitForGCInManaged(GCTask(GCTaskCause::EXPLICIT_CAUSE));
    thread->SafepointPoll();

    return JSTaggedValue::Undefined();
}

static GCTaskCause GCCauseFromString(JSThread *thread, const JSHandle<JSTaggedValue> &strCause)
{
    auto young = thread->GlobalConstants()->GetHandledYoungGCString();
    auto threshold = thread->GlobalConstants()->GetHandledThresholdGCString();
    auto mixed = thread->GlobalConstants()->GetHandledMixedGCString();
    auto full = thread->GlobalConstants()->GetHandledFullGCString();

    if (JSTaggedValue::StrictEqual(thread, young, strCause)) {
        return GCTaskCause::YOUNG_GC_CAUSE;
    }
    if (JSTaggedValue::StrictEqual(thread, threshold, strCause)) {
        return GCTaskCause::HEAP_USAGE_THRESHOLD_CAUSE;
    }
    if (JSTaggedValue::StrictEqual(thread, mixed, strCause)) {
        return GCTaskCause::MIXED;
    }
    if (JSTaggedValue::StrictEqual(thread, full, strCause)) {
        return GCTaskCause::OOM_CAUSE;
    }
    return GCTaskCause::INVALID_CAUSE;
}

/**
 * The function triggers specific GC.
 * @param gc_type - string denotes type of GC. Possible values are: "young", "threshold", "mixed", "full"
 * @param callback - function which will be executed during concurrent phase. If the parameter is specified the GC will
 * be executed in-place. The function may not be called if the GC doesn't have concurrent phases or the specified GC
 * type doesn't run concurrent phase or the task is canceled. the function accepts one parameter - gc marker.
 * The marker has 'markObjectrecursively' method which marks the argument object and the objects reachable from it.
 * @return gc id. The id should be passed to waitForFinishGC to ensure the GC is finished.
 *  - The function may return 0 in case the GC is executed in-place. It means there is no need to wait such GC.
 *  - The function may return -1 in case the task is canceled.
 */
JSTaggedValue global::StartGC(EcmaRuntimeCallInfo *argv)
{
    JSThread *thread = argv->GetThread();
    ASSERT(thread != nullptr);
    [[maybe_unused]] EcmaHandleScope handleScope(thread);
    BUILTINS_API_TRACE(thread, Global, StartGC);
    bool runGcInPlace = Runtime::GetOptions().IsRunGcInPlace("ecmascript");

    GCTaskCause reason = GCCauseFromString(thread, builtins_common::GetCallArg(argv, 0));
    JSHandle<JSTaggedValue> callbackFn = builtins_common::GetCallArg(argv, 1);
    mem::GC *gc = thread->GetEcmaVM()->GetGC();
    if (!gc->CheckGCCause(reason)) {
        JSHandle<JSObject> err =
            thread->GetEcmaVM()->GetFactory()->GetJSError(ErrorType::TYPE_ERROR, "Invalid GC cause for this GC type");
        THROW_NEW_ERROR_AND_RETURN_VALUE(thread, err.GetTaggedValue(), JSTaggedValue::Exception());
    }
    g_gGctaskTracker.InitIfNeeded(gc);
    auto task = MakePandaUnique<GCTask>(reason);
    if (!callbackFn->IsUndefined()) {
        if (!callbackFn->IsJSFunction()) {
            JSHandle<JSObject> err =
                thread->GetEcmaVM()->GetFactory()->GetJSError(ErrorType::TYPE_ERROR, "Invalid GC callback");
            THROW_NEW_ERROR_AND_RETURN_VALUE(thread, err.GetTaggedValue(), JSTaggedValue::Exception());
        }
        runGcInPlace = true;
        uintptr_t globalHandle =
            thread->GetEcmaGlobalStorage()->NewGlobalHandle(callbackFn.GetTaggedValue().GetRawData());
        g_gGctaskTracker.SetCallbackForTask(task->GetId(), globalHandle);
    }
    if (reason == GCTaskCause::YOUNG_GC_CAUSE) {
        runGcInPlace = true;
    }

    bool taskExecuted = false;
    uint32_t id = task->GetId();
    if (runGcInPlace) {
        taskExecuted = gc->WaitForGCInManaged(*task);
    } else {
        g_gGctaskTracker.AddTaskId(id);
        taskExecuted = gc->Trigger(std::move(task));
    }
    if (runGcInPlace) {
        return taskExecuted ? JSTaggedValue(0) : JSTaggedValue(-1);
    }
    if (!taskExecuted) {
        g_gGctaskTracker.RemoveId(id);
        return JSTaggedValue(-1);
    }
    return JSTaggedValue(static_cast<double>(id));
}

/**
 * The function returns when the specified GC gets finished.
 * @param gc_id - id of the GC which is returned by startGc.
 * If gc_id is 0 or -1 the function returns immediately.
 */
JSTaggedValue global::WaitForFinishGC(EcmaRuntimeCallInfo *argv)
{
    JSThread *thread = argv->GetThread();
    [[maybe_unused]] EcmaHandleScope handleScope(thread);
    BUILTINS_API_TRACE(thread, Global, WaitForFinishGC);
    JSHandle<JSTaggedValue> arg = builtins_common::GetCallArg(argv, 0);
    if (!arg->IsNumber()) {
        JSHandle<JSObject> err = thread->GetEcmaVM()->GetFactory()->GetJSError(ErrorType::TYPE_ERROR, "Invalid GC id");
        THROW_NEW_ERROR_AND_RETURN_VALUE(thread, err.GetTaggedValue(), JSTaggedValue::Exception());
    }
    auto id = static_cast<int64_t>(arg->GetNumber());
    if (id <= 0) {
        return JSTaggedValue::Undefined();
    }
    ASSERT(g_gGctaskTracker.IsInitialized());
    ScopedNativeCodeThread s(thread);
    while (g_gGctaskTracker.HasId(static_cast<uint64_t>(id))) {
        constexpr uint64_t WAIT_TIME_MS = 10;
        os::thread::NativeSleep(WAIT_TIME_MS);
    }
    return JSTaggedValue::Undefined();
}

// Function schedules GC before n-th allocation by setting counter to the specific GC trigger.
// Another call may reset the counter.  In this case the last counter will be used to trigger the GC.
JSTaggedValue global::ScheduleGcAfterNthAlloc(EcmaRuntimeCallInfo *argv)
{
    JSThread *thread = argv->GetThread();
    [[maybe_unused]] EcmaHandleScope handleScope(thread);
    BUILTINS_API_TRACE(thread, Global, ScheduleGcAfterNthAlloc);
    JSHandle<JSTaggedValue> arg = builtins_common::GetCallArg(argv, 0);
    if (!arg->IsInt()) {
        JSHandle<JSObject> err =
            thread->GetEcmaVM()->GetFactory()->GetJSError(ErrorType::TYPE_ERROR, "Invalid argument");
        THROW_NEW_ERROR_AND_RETURN_VALUE(thread, err.GetTaggedValue(), JSTaggedValue::Exception());
    }
    auto counter = static_cast<int>(arg->GetInt());
    if (counter < 0) {
        JSHandle<JSObject> err =
            thread->GetEcmaVM()->GetFactory()->GetJSError(ErrorType::TYPE_ERROR, "Invalid argument");
        THROW_NEW_ERROR_AND_RETURN_VALUE(thread, err.GetTaggedValue(), JSTaggedValue::Exception());
    }
    GCTaskCause reason = GCCauseFromString(thread, builtins_common::GetCallArg(argv, 1));
    if (reason == GCTaskCause::INVALID_CAUSE) {
        JSHandle<JSObject> err =
            thread->GetEcmaVM()->GetFactory()->GetJSError(ErrorType::TYPE_ERROR, "Invalid GC cause");
        THROW_NEW_ERROR_AND_RETURN_VALUE(thread, err.GetTaggedValue(), JSTaggedValue::Exception());
    }

    EcmaVM *vm = thread->GetEcmaVM();
    mem::GCTrigger *trigger = vm->GetGCTrigger();
    if (trigger->GetType() != mem::GCTriggerType::ON_NTH_ALLOC) {
        JSHandle<JSObject> err = thread->GetEcmaVM()->GetFactory()->GetJSError(
            ErrorType::TYPE_ERROR, "VM is running with unsupported GC trigger");
        THROW_NEW_ERROR_AND_RETURN_VALUE(thread, err.GetTaggedValue(), JSTaggedValue::Exception());
    }
    auto schedTrigger = reinterpret_cast<mem::SchedGCOnNthAllocTrigger *>(vm->GetGCTrigger());
    schedTrigger->ScheduleGc(reason, counter);

    return JSTaggedValue::Undefined();
}

JSTaggedValue global::IsScheduledGcTriggered(EcmaRuntimeCallInfo *argv)
{
    JSThread *thread = argv->GetThread();
    BUILTINS_API_TRACE(thread, Global, IsScheduledGcTriggered);

    EcmaVM *vm = thread->GetEcmaVM();
    mem::GCTrigger *trigger = vm->GetGCTrigger();
    if (trigger->GetType() != mem::GCTriggerType::ON_NTH_ALLOC) {
        return JSTaggedValue::Undefined();
    }
    auto schedTrigger = reinterpret_cast<mem::SchedGCOnNthAllocTrigger *>(vm->GetGCTrigger());
    return JSTaggedValue(schedTrigger->IsTriggered());
}

JSTaggedValue global::AllocateArrayObject(EcmaRuntimeCallInfo *argv)
{
    JSThread *thread = argv->GetThread();
    ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
    ASSERT(thread != nullptr);
    [[maybe_unused]] EcmaHandleScope handleScope(thread);
    BUILTINS_API_TRACE(thread, Global, AllocateArrayObject);

    int64_t sizeInBytes = 0;

    if (builtins_common::GetCallArg(argv, 0)->IsInt()) {
        sizeInBytes = builtins_common::GetCallArg(argv, 0)->GetInt();
    } else if (builtins_common::GetCallArg(argv, 0)->IsDouble()) {
        sizeInBytes = builtins_common::GetCallArg(argv, 0)->GetDouble();
    } else {
        JSHandle<JSObject> err =
            thread->GetEcmaVM()->GetFactory()->GetJSError(ErrorType::TYPE_ERROR, "The value must be an integer");
        THROW_NEW_ERROR_AND_RETURN_VALUE(thread, err.GetTaggedValue(), JSTaggedValue::Exception());
    }

    if (sizeInBytes < 0) {
        JSHandle<JSObject> err =
            thread->GetEcmaVM()->GetFactory()->GetJSError(ErrorType::TYPE_ERROR, "The value must be positive");
        THROW_NEW_ERROR_AND_RETURN_VALUE(thread, err.GetTaggedValue(), JSTaggedValue::Exception());
    }

    sizeInBytes = RoundUp(sizeInBytes, sizeof(TaggedType)) - JSArray::SIZE;

    if (sizeInBytes < 0) {
        sizeInBytes = 0;
    }

    uint32_t numElements = sizeInBytes / sizeof(TaggedType);
    JSHandle<JSArray> array = factory->NewJSArray();

    if (numElements > 0) {
        JSHandle<TaggedArray> elements = factory->NewTaggedArray(numElements);

        if (elements.IsEmpty()) {
            return JSTaggedValue::Exception();
        }

        array->SetElements(thread, elements);
        array->SetArrayLength(thread, numElements);
    }

    return array.GetTaggedValue();
}

JSTaggedValue global::MarkObject(EcmaRuntimeCallInfo *argv)
{
    JSThread *thread = argv->GetThread();
    BUILTINS_API_TRACE(thread, Global, MarkObject);
    EcmaVM *vm = thread->GetEcmaVM();
    JSHandle<JSTaggedValue> arg = builtins_common::GetCallArg(argv, 0);
    if (arg->IsHeapObject()) {
        vm->MarkObject(arg->GetHeapObject());
    }
    return JSTaggedValue::Undefined();
}

JSTaggedValue global::GetMarkQueue(EcmaRuntimeCallInfo *argv)
{
    JSThread *thread = argv->GetThread();
    BUILTINS_API_TRACE(thread, Global, GetMarkQueue);
    EcmaVM *vm = thread->GetEcmaVM();
    [[maybe_unused]] EcmaHandleScope handleScope(thread);
    JSHandle<JSArray> array = vm->GetFactory()->NewJSArray();
    RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
    size_t index = 0;
    vm->IterateOverMarkQueue([&index, &array, thread](ObjectHeader *obj) {
        FastRuntimeStub::SetPropertyByIndex<true>(thread, array.GetTaggedValue(), index++, JSTaggedValue(obj));
    });
    return array.GetTaggedValue();
}

JSTaggedValue global::ClearMarkQueue(EcmaRuntimeCallInfo *argv)
{
    JSThread *thread = argv->GetThread();
    BUILTINS_API_TRACE(thread, Global, ClearMarkQueue);
    thread->GetEcmaVM()->ClearMarkQueue();
    return JSTaggedValue::Undefined();
}

JSTaggedValue gc_marker::MarkObjectRecursively(EcmaRuntimeCallInfo *argv)
{
    JSThread *thread = argv->GetThread();
    BUILTINS_API_TRACE(thread, GCMarker, MarkObjectRecursively);
    mem::GC *gc = thread->GetEcmaVM()->GetGC();
    JSHandle<JSTaggedValue> arg = builtins_common::GetCallArg(argv, 0);
    if (!arg->IsHeapObject()) {
        [[maybe_unused]] EcmaHandleScope handleScope(thread);
        THROW_TYPE_ERROR_AND_RETURN(thread, "Argument is not an object", JSTaggedValue::Exception());
    }

    ObjectHeader *obj = arg->GetHeapObject();
    PandaStack<ObjectHeader *> markStack;
    gc->MarkObject(obj);
    markStack.push(obj);
    while (!markStack.empty()) {
        obj = markStack.top();
        markStack.pop();
        mem::ObjectHelpers<LANG_TYPE_DYNAMIC>::TraverseAllObjects(
            obj, [gc, &markStack]([[maybe_unused]] ObjectHeader *object, ObjectHeader *ref) {
                if (ref != nullptr && gc->MarkObjectIfNotMarked(ref)) {
                    markStack.push(ref);
                }
            });
    }
    return JSTaggedValue::Undefined();
}

JSTaggedValue global::GetObjectSpaceType(EcmaRuntimeCallInfo *argv)
{
    ASSERT(argv != nullptr);
    JSThread *thread = argv->GetThread();
    ASSERT(thread != nullptr);
    BUILTINS_API_TRACE(thread, Global, GetObjectSpaceType);

    JSHandle<JSTaggedValue> objHandle = builtins_common::GetCallArg(argv, 0);
    auto globalConstants = thread->GlobalConstants();
    if (!objHandle.GetTaggedValue().IsHeapObject()) {
        [[maybe_unused]] EcmaHandleScope handleScope(thread);
        JSHandle<JSObject> err =
            thread->GetEcmaVM()->GetFactory()->GetJSError(ErrorType::TYPE_ERROR, "Non Heap Object");
        THROW_NEW_ERROR_AND_RETURN_VALUE(thread, err.GetTaggedValue(), JSTaggedValue::Exception());
    }

    SpaceType objSpaceType = PoolManager::GetMmapMemPool()->GetSpaceTypeForAddr(
        static_cast<void *>(objHandle.GetTaggedValue().GetHeapObject()));
    switch (objSpaceType) {
        case SpaceType::SPACE_TYPE_OBJECT:
            return GCSpecialisedObjectSpaceType(thread, objHandle).GetTaggedValue();
        case SpaceType::SPACE_TYPE_HUMONGOUS_OBJECT:
            return globalConstants->GetHandledHumongousObjectSpaceString().GetTaggedValue();
        case SpaceType::SPACE_TYPE_NON_MOVABLE_OBJECT:
            return globalConstants->GetHandledNonMovableObjectSpaceString().GetTaggedValue();
        default:
            return globalConstants->GetHandledUndefinedSpaceString().GetTaggedValue();
    }
}

JSHandle<JSTaggedValue> GCSpecialisedObjectSpaceType(JSThread *thread, const JSHandle<JSTaggedValue> &objHandle)
{
    auto globalConstants = thread->GlobalConstants();
    if (!thread->GetEcmaVM()->GetGC()->IsGenerational()) {
        return globalConstants->GetHandledObjectSpaceString();
    }

    auto heapManager = thread->GetEcmaVM()->GetHeapManager();
    if (heapManager->IsObjectInYoungSpace(objHandle.GetTaggedValue().GetHeapObject())) {
        return globalConstants->GetHandledYoungSpaceString();
    }
    return globalConstants->GetHandledTenuredSpaceString();
}

JSTaggedValue global::PinObject(EcmaRuntimeCallInfo *argv)
{
    JSThread *thread = argv->GetThread();
    BUILTINS_API_TRACE(thread, Global, PinObject);
    EcmaVM *vm = thread->GetEcmaVM();
    JSHandle<JSTaggedValue> arg = builtins_common::GetCallArg(argv, 0);
    if (arg->IsHeapObject()) {
        auto *gc = vm->GetGC();
        if (!gc->IsPinningSupported()) {
            [[maybe_unused]] EcmaHandleScope handleScope(thread);
            JSHandle<JSObject> err =
                vm->GetFactory()->GetJSError(ErrorType::TYPE_ERROR, "Current GC does not support pinning");
            THROW_NEW_ERROR_AND_RETURN_VALUE(thread, err.GetTaggedValue(), JSTaggedValue::Exception());
        }
        vm->GetHeapManager()->PinObject(arg->GetHeapObject());
    } else {
        [[maybe_unused]] EcmaHandleScope handleScope(thread);
        JSHandle<JSObject> err = vm->GetFactory()->GetJSError(ErrorType::TYPE_ERROR, "The value must be an object");
        THROW_NEW_ERROR_AND_RETURN_VALUE(thread, err.GetTaggedValue(), JSTaggedValue::Exception());
    }
    return JSTaggedValue::Undefined();
}

JSTaggedValue global::UnpinObject(EcmaRuntimeCallInfo *argv)
{
    JSThread *thread = argv->GetThread();
    BUILTINS_API_TRACE(thread, Global, UnpinObject);
    EcmaVM *vm = thread->GetEcmaVM();
    JSHandle<JSTaggedValue> arg = builtins_common::GetCallArg(argv, 0);
    if (arg->IsHeapObject()) {
        vm->GetHeapManager()->UnpinObject(arg->GetHeapObject());
    } else {
        [[maybe_unused]] EcmaHandleScope handleScope(thread);
        JSHandle<JSObject> err = vm->GetFactory()->GetJSError(ErrorType::TYPE_ERROR, "The value must be an object");
        THROW_NEW_ERROR_AND_RETURN_VALUE(thread, err.GetTaggedValue(), JSTaggedValue::Exception());
    }
    return JSTaggedValue::Undefined();
}

JSTaggedValue global::GetObjectAddress(EcmaRuntimeCallInfo *argv)
{
    JSThread *thread = argv->GetThread();
    BUILTINS_API_TRACE(thread, Global, GetObjectAddress);
    [[maybe_unused]] EcmaHandleScope handleScope(thread);
    PandaStringStream addrStream;
    addrStream << builtins_common::GetCallArg(argv, 0)->GetHeapObject();
    return thread->GetEcmaVM()->GetFactory()->NewFromString(addrStream.str()).GetTaggedValue();
}

JSTaggedValue global::CallJsBoundFunction(EcmaRuntimeCallInfo *argv)
{
    JSThread *thread = argv->GetThread();
    BUILTINS_API_TRACE(thread, Global, CallJsBoundFunction);
    [[maybe_unused]] EcmaHandleScope handleScope(thread);
    // argv contains jsfunc, this, arg1,...

    JSHandle<JSBoundFunction> boundFunc(builtins_common::GetConstructor(argv));
    JSHandle<JSTaggedValue> thisObj(thread, boundFunc->GetBoundThis());
    argv->SetThis(thisObj.GetTaggedValue());
    return SlowRuntimeHelper::CallBoundFunction(argv);
}

JSTaggedValue global::CallJsProxy(EcmaRuntimeCallInfo *argv)
{
    JSThread *thread = argv->GetThread();
    BUILTINS_API_TRACE(thread, Global, CallJsProxy);
    [[maybe_unused]] EcmaHandleScope handleScope(thread);
    // argv contains js_proxy, this, arg1,...
    JSHandle<JSProxy> proxy(builtins_common::GetConstructor(argv));
    if (!proxy->IsCallable()) {
        THROW_TYPE_ERROR_AND_RETURN(thread, "Proxy target is not callable", JSTaggedValue::Undefined());
    }

    // Calling proxy directly should transfer 'undefined' as this
    return JSProxy::CallInternal(argv);
}

#if ECMASCRIPT_ENABLE_RUNTIME_STAT
JSTaggedValue global::StartRuntimeStat(EcmaRuntimeCallInfo *argv)
{
    JSThread *thread = argv->GetThread();
    [[maybe_unused]] EcmaHandleScope handleScope(thread);
    // start vm runtime stat statistic
    thread->GetEcmaVM()->SetRuntimeStatEnable(true);
    return JSTaggedValue::Undefined();
}

JSTaggedValue global::StopRuntimeStat(EcmaRuntimeCallInfo *argv)
{
    JSThread *thread = argv->GetThread();
    [[maybe_unused]] EcmaHandleScope handleScope(thread);
    // start vm runtime stat statistic
    thread->GetEcmaVM()->SetRuntimeStatEnable(false);
    return JSTaggedValue::Undefined();
}
#endif
}  // namespace ark::ecmascript::builtins
